use common::error::with_context;
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use std::fs;
use std::path::{Path, PathBuf, StripPrefixError};
use std::sync::mpsc::channel;
use std::time::Duration;

//const WATCHER_DURATION: u64 = 1;

const FILE_EXTENSIONS: [&str; 3] = ["ts", "vue", "png"];
pub fn sync(source_dir: &Path, target_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
    // 创建一个通道来接收文件系统事件
    let (tx, rx) = channel();

    // 创建一个文件系统观察者
    let mut watcher = RecommendedWatcher::new(tx, Config::default())?;

    // 监视 module_dir_info.src_path() 目录及其子目录
    watcher.watch(source_dir, RecursiveMode::Recursive)?;

    loop {
        match rx.recv() {
            Ok(event) => {
                if let Ok(Event { kind, paths, .. }) = event {
                    let valid_paths = get_valid_paths(&paths.iter().collect::<Vec<&PathBuf>>());

                    for path in valid_paths {
                        let target_path =
                            get_target_path(source_dir, target_dir, path).map_err(|err| {
                                with_context(
                                    err,
                                    &format!(
                                        r#"get_target_path error:
source_dir:{:?}
target_dir:{:?}
path:{:?}"#,
                                        source_dir, target_dir, path
                                    ),
                                )
                            })?;

                        match kind {
                            EventKind::Create(_) | EventKind::Modify(_) => {
                                if let Some(parent_dir) = target_path.parent() {
                                    fs::create_dir_all(parent_dir)?;
                                }
                                fs::copy(&path, &target_path)?;
                            }
                            EventKind::Remove(_) => {
                                if target_path.exists() {
                                    fs::remove_file(target_path)?;
                                }
                            }
                            _ => {
                                eprintln!("无效的同步文件:{:?}", path);
                            }
                        }
                    }
                }
            }
            Err(e) => println!("watch error: {:?}", e),
        }
    }
}

fn get_valid_paths<'a>(paths: &[&'a PathBuf]) -> Vec<&'a PathBuf> {
    paths
        .iter()
        .filter(|path| {
            path.extension()
                .map(|ext| FILE_EXTENSIONS.iter().any(|&e| e == ext))
                .unwrap_or_default()
        })
        .map(|path| *path)
        .collect()
}

fn get_target_path(
    source_dir: &Path,
    target_dir: &Path,
    file_path: &Path,
) -> Result<PathBuf, StripPrefixError> {
    let relative_path = file_path.strip_prefix(source_dir)?;
    Ok(target_dir.join(relative_path))
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_get_valid_paths_with_valid_extensions() {
        let paths = vec![
            PathBuf::from("file1.ts"),
            PathBuf::from("file2.vue"),
            PathBuf::from("image.png"),
        ];

        let paths_refs: Vec<&PathBuf> = paths.iter().collect();
        let valid_paths = get_valid_paths(&paths_refs);

        assert_eq!(valid_paths.len(), 3);
        assert!(valid_paths.contains(&&paths[0]));
        assert!(valid_paths.contains(&&paths[1]));
        assert!(valid_paths.contains(&&paths[2]));
    }

    #[test]
    fn test_get_valid_paths_with_invalid_extensions() {
        let paths = vec![
            PathBuf::from("file1.txt"),
            PathBuf::from("file2.jpg"),
            PathBuf::from("script.js"),
        ];

        let paths_refs: Vec<&PathBuf> = paths.iter().collect();
        let valid_paths = get_valid_paths(&paths_refs);

        assert_eq!(valid_paths.len(), 0);
    }

    #[test]
    fn test_get_valid_paths_with_mixed_extensions() {
        let paths = vec![
            PathBuf::from("file1.ts"),
            PathBuf::from("file2.txt"),
            PathBuf::from("image.png"),
            PathBuf::from("notes.md"),
        ];

        let paths_refs: Vec<&PathBuf> = paths.iter().collect();
        let valid_paths = get_valid_paths(&paths_refs);

        assert_eq!(valid_paths.len(), 2);
        assert!(valid_paths.contains(&&paths[0]));
        assert!(valid_paths.contains(&&paths[2]));
    }

    #[test]
    fn test_get_valid_paths_with_no_extensions() {
        let paths = vec![
            PathBuf::from("file1"),
            PathBuf::from("file2."),
            PathBuf::from("README"),
        ];

        let paths_refs: Vec<&PathBuf> = paths.iter().collect();
        let valid_paths = get_valid_paths(&paths_refs);

        assert_eq!(valid_paths.len(), 0);
    }

    #[test]
    fn test_get_target_path() -> Result<(), Box<dyn std::error::Error>> {
        let source_dir = Path::new("/source");
        let target_dir = Path::new("/target");
        let file_path = Path::new("/source/folder/file.txt");

        let expected_target_path = Path::new("/target/folder/file.txt");
        let actual_target_path = get_target_path(source_dir, target_dir, file_path)?;

        assert_eq!(actual_target_path, expected_target_path);
        Ok(())
    }

    #[test]
    fn test_get_target_path_with_root_file() -> Result<(), Box<dyn std::error::Error>> {
        let source_dir = Path::new("/source");
        let target_dir = Path::new("/target");
        let file_path = Path::new("/source/file.txt");

        let expected_target_path = Path::new("/target/file.txt");
        let actual_target_path = get_target_path(source_dir, target_dir, file_path)?;

        assert_eq!(actual_target_path, expected_target_path);
        Ok(())
    }

    #[test]
    fn test_get_target_path_error() {
        let source_dir = Path::new("/source");
        let target_dir = Path::new("/target");
        let file_path = Path::new("/wrong_source/file.txt");

        assert!(get_target_path(source_dir, target_dir, file_path).is_err());
    }

    #[test]
    fn test_get_target_path_with_real_case() -> Result<(), Box<dyn std::error::Error>> {
        let source_dir = Path::new("/someone/module/src");
        let target_dir = Path::new("/someone/module/.ppt/temp_project/src");
        let file_path = Path::new("/someone/module/src/TodoList.vue");

        let expected_target_path = Path::new("/someone/module/.ppt/temp_project/src/TodoList.vue");
        let actual_target_path = get_target_path(source_dir, target_dir, file_path)?;

        assert_eq!(actual_target_path, expected_target_path);
        Ok(())
    }

    #[test]
    fn test_get_target_path_with_relative_error_case() -> Result<(), Box<dyn std::error::Error>> {
        let source_dir = Path::new("src");
        let target_dir = Path::new(".ppt/temp_project/src");
        let file_path = Path::new("/someone/module/src/TodoList.vue");

        let expected_target_path = Path::new("/someone/module/.ppt/temp_project/src/TodoList.vue");
        assert!(get_target_path(source_dir, target_dir, file_path).is_err());

        Ok(())
    }
}
